#include "workCmd.h"
#include "md5.h"

int recvn(int sockfd, void *buf, int length){
    int total = 0;
    char *p = (char *)buf;
    while(total < length){
        ssize_t sret = recv(sockfd,p+total,length-total,0);
        total += sret;
    }
    return total;
}

int upload(int netFd,char *file,int userId){
    //打开文件之前需要先获取文件在本地的真实名称
    int code = 0;
    getUserCode(userId,&code);
    char md5Str[MD5_STR_LEN + 1] = {0};
    // 根据文件名、user_id和用户当前目录id，在虚拟文件表中找到md5值
    getVirtualFileMd5(file,userId,code,md5Str);
    char realFileName[1024] = {0};

    // 根据文件名md5值获取本地的真实文件名
    getRealFileName(md5Str, realFileName);
    char path[1024] = {0};
    sprintf(path, "%s%s%s", "NetDisk", "/", realFileName);
    int fd = open(path, O_RDWR);
    printf("Path = %s\n", path);

    train_t train;
    bzero(&train, sizeof(train));
    // 打开文件错误需要通知对面
    if(fd == -1){
        perror("open");
        train.length = 0;
        send(netFd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
    }
    // 发送文件名
    train.length = strlen(file);
    strcpy(train.data, file);
    int ret = send(netFd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
    ERROR_CHECK(ret, -1, "send");

    // 发送文件总大小
    struct stat statbuf;
    ret = fstat(fd, &statbuf);
    ERROR_CHECK(ret, -1, "fstat");
    train.length = 4;
    int fileSize = statbuf.st_size;
    memcpy(train.data, &fileSize, sizeof(fileSize));
    send(netFd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);

    // 获得文件已传大小
    int exitSize = 0;
    recvn(netFd, &exitSize, sizeof(exitSize));

    // 发送文件内容
    char *p = (char*)mmap(NULL, fileSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    int total = exitSize;
    printf("exitSize = %d\n", exitSize);
    printf("fileSize = %d\n", fileSize);
    if(fileSize >= 1073741824){
        //大文件
        printf("Hurge file .\n");
        send(netFd, p, fileSize, MSG_NOSIGNAL);
    }else{
        //小文件
        while (total < fileSize){
            if (fileSize - total < sizeof(train.data)){
                train.length = fileSize - total;
            }else{
                train.length = sizeof(train.data);
            }
            memcpy(train.data, p + total, train.length);
            total += train.length;
            send(netFd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
        }
        // 发送长度为0代表发送结束
        train.length = 0;
        ret = send(netFd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
        ERROR_CHECK(ret, -1, "send");
    }

    close(fd);
    munmap(p, fileSize);

    int total_time;
    recvn(netFd, &total_time, sizeof(int));
    printf("total time = %ds\n",total_time);

    return 0;
}

int Dowload(int sockFd,int userId){
    //获取文件名字
    char name[1024] = {0};
    int dataLength = 0;
    recvn(sockFd,&dataLength,sizeof(dataLength));
    recvn(sockFd,name,dataLength);
    if(dataLength == 0){
        printf("Wrong file name.\n");
        return EXIT_FAILURE;
    }
    printf("name = %s\n",name);

    //获取文件大小，没有就创建
    int filesize = 0;
    recvn(sockFd,&dataLength,sizeof(dataLength));
    recvn(sockFd,&filesize,dataLength);

    //获取md5
    char md5Str[MD5_STR_LEN + 1] = {0};
    recvn(sockFd, &dataLength, sizeof(int));
    recvn(sockFd, md5Str, dataLength);

    //获取当前目录id
    int code = 0;
    getUserCode(userId,&code);
    //查看vfs是否存在这个文件，存在就不插入这个表中
    int retFile = getVirtualFileMd5(name, userId, code, md5Str);
    if(retFile != 0)
    {
        // 文件不存在，在虚拟文件表中插入
        addVirtualFile(code, name, userId, md5Str, filesize);
    }

    // 计时
    time_t timeBeg, timeEnd;
    timeBeg = time(NULL);

    // 检查文件在服务器本地是否存在
    // 因为可能出现插入了虚拟文件表中在服务器本地却被删除了的情况
    int check = 0;
    int ret = ifLocalExist(md5Str);
    char path[1024] = {0};
    char realName[1024] = {0};
    int exitSize = 0;
    char buf[1000] = {0};
    int fd = 0;
    if(ret == 0){
        // 文件在本地存在
        // 1. 获取文件本地真名
        getRealFileName(md5Str, realName);
        sprintf(path, "%s%s%s", "NetDisk", "/", realName);
        // 2. 打开文件
        fd = open(path, O_RDWR | O_CREAT, 0666); 
        // 文件在本地存在，考虑误删了本地文件但表里的信息未更新
        ERROR_CHECK(fd, -1, "open");
        // 3. 获取文件大小已有大小
        struct stat statbuf;
        ret = fstat(fd, &statbuf);
        ERROR_CHECK(ret, -1, "fstat");
        exitSize = statbuf.st_size;
        // 4. 判断是否需要断点续传
        if(exitSize < filesize){
            // 需要上传，断点续传
            check = 1;
            // 通知客户端文件需要上传
            send(sockFd, &check, sizeof(int), MSG_NOSIGNAL);
        }else{
            // 不需要上传，秒传，通知对方
            send(sockFd, &check, sizeof(int), MSG_NOSIGNAL); 
            // 将本地文件表中相应文件的link数加1
            if (retFile != 0){ 
                // 文件原先在虚拟文件表中不存在，链接数加1
                addLink(md5Str);
            }

            timeEnd = time(NULL);
            int total_time = timeEnd - timeBeg;
            printf("total time = %ds\n", total_time);
            send(sockFd, &total_time, sizeof(int), MSG_NOSIGNAL);
            return 0;
        }
    }else{
        // 文件在本地不存在
        check = 1;
        // 通知客户端文件需要上传
        send(sockFd, &check, sizeof(int), MSG_NOSIGNAL);
        // 1. 拼一个本地文件名
        bzero(path, sizeof(path));
        sprintf(realName, "%s%d%d", name, userId, code);
        // 2. 添加到本地文件表中
        addLocalFile(md5Str, realName);
        // 3. 拼出一个文件路径
        sprintf(path, "%s%s%s", "NetDisk", "/", realName);
        fd = open(path, O_RDWR | O_CREAT, 0666); 
        ERROR_CHECK(fd, -1, "open");
        // 4. 获取文件大小，这里肯定不存在，只是为了客户端不阻塞
        struct stat statbuf;
        ret = fstat(fd, &statbuf);
        ERROR_CHECK(ret, -1, "fstat");
        exitSize = statbuf.st_size;
    }
    // 发送本地文件大小
    send(sockFd, &exitSize, sizeof(exitSize), MSG_NOSIGNAL);
    lseek(fd, 0, SEEK_END);

    if(filesize >= 1073741824){
        printf("Hurge file.\n");
        ftruncate(fd, filesize); 
        char *p = (char *)mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        ERROR_CHECK(p, MAP_FAILED, "mmap");
        recvn(sockFd, p, filesize);
        munmap(p, filesize);
        timeEnd = time(NULL);
    }else{
        //小文件
        printf("Tiny file.\n");
        while (1){
            bzero(buf, sizeof(buf));
            ret = recv(sockFd, &dataLength, sizeof(int), MSG_NOSIGNAL);
            recvn(sockFd, buf, dataLength);
            if (dataLength == 0){
                timeEnd = time(NULL);
                break;
            }
            if(ret == 0){
                // ret为0说明对面提前断开了
                close(fd);
                timeEnd = time(NULL);
                int totalTime = timeEnd - timeBeg;
                printf("total time = %ds\n", totalTime);
                return EXIT_FAILURE;
            }
            write(fd, buf, dataLength);
        }
    }
    close(fd);
    int totalTime = timeEnd - timeBeg;
    printf("Total time = %ds\n", totalTime);
    send(sockFd, &totalTime, sizeof(totalTime), MSG_NOSIGNAL);

    return 0;
} 
